function mxCellEditor(graph)
{
	this.graph = graph;
	this.textarea = document.createElement('textarea');
	this.textarea.className = 'mxCellEditor';
	this.textarea.style.position = 'absolute';
	this.textarea.style.overflow = 'visible';
	this.textarea.setAttribute('cols', '20');
	this.textarea.setAttribute('rows', '4');

	this.init();
};
mxCellEditor.prototype.graph = null;
mxCellEditor.prototype.textarea = null;
mxCellEditor.prototype.editingCell = null;
mxCellEditor.prototype.trigger = null;
mxCellEditor.prototype.modified = false;
mxCellEditor.prototype.emptyLabelText = '';
mxCellEditor.prototype.textNode = '';
mxCellEditor.prototype.init = function()
{
	var self = this;
	mxEvent.addListener(this.textarea, 'blur', function(evt)
	{
		self.stopEditing(!self.graph.isInvokesStopCellEditing());
	});
	mxEvent.addListener(this.textarea, 'keydown', function(evt)
	{
		if (self.clearOnChange)
		{
			self.clearOnChange = false;
			self.textarea.value = '';
		}
		self.setModified(true);
	});
};
mxCellEditor.prototype.isModified = function()
{
	return this.modified;
};
mxCellEditor.prototype.setModified = function(value)
{
	this.modified = value;
};
mxCellEditor.prototype.startEditing = function(cell, trigger)
{
	this.stopEditing(true);
	var state = this.graph.getView().getState(cell);

	if (state != null)
	{
		this.editingCell = cell;
		this.trigger = trigger;
		this.textNode = null;

		if (state.text != null)
		{
			if (this.isHideLabel(state))
			{
				this.textNode = state.text.node;
				this.textNode.style.visibility = 'hidden';
			}
			var scale = this.graph.getView().scale;
			this.textarea.style.fontSize = state.text.size * scale;
			this.textarea.style.fontFamily = state.text.family;
			this.textarea.style.color = state.text.color;

			if (this.textarea.style.color == 'white')
			{
				this.textarea.style.color = 'black';
			}
			this.textarea.style.textAlign = (this.graph.model.isEdge(state.cell))
				? 'left' : (state.text.align || 'left');
			this.textarea.style.fontWeight = state.text.isStyleSet(mxConstants.FONT_BOLD) ? 'bold' : 'normal';
		}
		var bounds = this.getEditorBounds(state);
		this.textarea.style.left = bounds.x + 'px';
		this.textarea.style.top = bounds.y + 'px';
		this.textarea.style.width = bounds.width + 'px';
		this.textarea.style.height = bounds.height + 'px';
		this.textarea.style.zIndex = 5;
		var value = this.getInitialValue(state, trigger);

		if (value == null || value.length == 0)
		{
			value = this.getEmptyLabelText();
			this.clearOnChange = true;
		}
		else
		{
			this.clearOnChange = false;
		}
		this.setModified(false);
		this.textarea.value = value;
		this.graph.container.appendChild(this.textarea);
		this.textarea.focus();
		this.textarea.select();
	}
};
mxCellEditor.prototype.stopEditing = function(cancel)
{
	cancel = cancel || false;

	if (this.editingCell != null)
	{
		if (this.textNode != null)
		{
			this.textNode.style.visibility = 'visible';
			this.textNode = null;
		}

		if (!cancel && this.isModified())
		{
			this.graph.labelChanged(this.editingCell, this.getCurrentValue(), this.trigger);
		}
		this.editingCell = null;
		this.trigger = null;
		this.textarea.blur();
		this.textarea.parentNode.removeChild(this.textarea);
	}
};
mxCellEditor.prototype.getInitialValue = function(state, trigger)
{
	return this.graph.getEditingValue(state.cell, trigger);
};
mxCellEditor.prototype.getCurrentValue = function()
{
	return this.textarea.value.replace(/\r/g, '');
};
mxCellEditor.prototype.isHideLabel = function(state)
{
	return true;
};
mxCellEditor.prototype.getEditorBounds = function(state)
{
	var isEdge = this.graph.getModel().isEdge(state.cell);
	var scale = this.graph.getView().scale;
	var minHeight = (state.text == null) ? 30 : state.text.size * scale + 20;
	var minWidth = (this.textarea.style.textAlign == 'left') ? 120 : 40;
	var spacing = parseInt(state.style[mxConstants.STYLE_SPACING] || 2) * scale;
	var spacingTop = (parseInt(state.style[mxConstants.STYLE_SPACING_TOP] || 0)) * scale + spacing;
	var spacingRight = (parseInt(state.style[mxConstants.STYLE_SPACING_RIGHT] || 0)) * scale + spacing;
	var spacingBottom = (parseInt(state.style[mxConstants.STYLE_SPACING_BOTTOM] || 0)) * scale + spacing;
	var spacingLeft = (parseInt(state.style[mxConstants.STYLE_SPACING_LEFT] || 0)) * scale + spacing;
	var result = new mxRectangle(state.x, state.y, Math.max(minWidth, state.width - spacingLeft - spacingRight),
		Math.max(minHeight, state.height - spacingTop - spacingBottom));

	if (isEdge)
	{
		result.x = state.absoluteOffset.x;
		result.y = state.absoluteOffset.y;

		if (state.text != null && state.text.boundingBox != null)
		{
			result.x = state.text.boundingBox.x;
			result.y = state.text.boundingBox.y;
		}
	}
	else if (state.text != null && state.text.boundingBox != null)
	{
		result.x = Math.min(result.x, state.text.boundingBox.x);
		result.y = Math.min(result.y, state.text.boundingBox.y);
	}
	result.x += spacingLeft;
	result.y += spacingTop;

	if (state.text != null && state.text.boundingBox != null)
	{
		if (!isEdge)
		{
			result.width = Math.max(result.width, state.text.boundingBox.width);
			result.height = Math.max(result.height, state.text.boundingBox.height);
		}
		else
		{
			result.width = Math.max(minWidth, state.text.boundingBox.width);
			result.height = Math.max(minHeight, state.text.boundingBox.height);
		}
	}

	if (this.graph.getModel().isVertex(state.cell))
	{
		var horizontal = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);

		if (horizontal == mxConstants.ALIGN_LEFT)
		{
			result.x -= state.width;
		}
		else if (horizontal == mxConstants.ALIGN_RIGHT)
		{
			result.x += state.width;
		}
		var vertical =
			mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);

		if (vertical == mxConstants.ALIGN_TOP)
		{
			result.y -= state.height;
		}
		else if (vertical == mxConstants.ALIGN_BOTTOM)
		{
			result.y += state.height;
		}
	}
	return result;
};
mxCellEditor.prototype.getEmptyLabelText = function(cell)
{
	return this.emptyLabelText;
};
mxCellEditor.prototype.getEditingCell = function()
{
	return this.editingCell;
};
mxCellEditor.prototype.destroy = function()
{
	mxEvent.release(this.textarea);

	if (this.textarea.parentNode != null)
	{
		this.textarea.parentNode.removeChild(this.textarea);
	}
	this.textarea = null;
};